package com.star.learn.excel.component;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.star.learn.excel.domain.Student;
import com.star.learn.excel.service.RabbitMQProducerService;
import com.star.learn.excel.service.RedisMetadataService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@Slf4j
public class BackupRecoveryTask {

    private static final String BACKUP_DIR = "backup";

    // 文件名格式：fileId_sheetName_batchNum_timestamp.json
    private static final Pattern FILE_PATTERN = Pattern.compile("^(.*?)_(.*?)_(\\d+)_\\d+\\.json$");

    @Resource
    private RabbitMQProducerService producerService; // 用于发送触发消息

    @Resource
    private RedisMetadataService redisMetadataService; // 用于存储数据到 Redis 和更新元数据

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 1.处理 Excel 解析器在存储 Redis 失败时产生的本地备份
    @Scheduled(fixedDelay = 60 * 1000) // 每1分钟扫描一次
    private void recoverFromLocalBackups() {
        log.info("开始执行备份恢复任务");
        File backupFolder = new File(BACKUP_DIR);
        if (!backupFolder.exists() || !backupFolder.isDirectory()) {
            log.debug("Backup 目录不存在或不是一个目录: {}", backupFolder.getAbsolutePath());
            return;
        }

        File[] files = backupFolder.listFiles((dir, name) -> name.endsWith(".json"));
        if (files == null || files.length == 0) {
            log.debug("没有待恢复的备份文件在目录: {}", backupFolder.getAbsolutePath());
            return;
        }

        log.info("发现 {} 个本地备份文件待恢复.", files.length);

        for (File file : files) {
            String fileName = file.getName();
            log.info("尝试恢复本地备份文件: {}", fileName);

            try {
                Matcher matcher = FILE_PATTERN.matcher(fileName);
                if (!matcher.matches()) {
                    log.warn("备份文件名格式非法，跳过: {}", fileName);
                    continue;
                }

                String fileId = matcher.group(1);
                String sheetName = matcher.group(2);
                int batchNum = Integer.parseInt(matcher.group(3));

                // 检查 Redis 元数据状态，如果已经是 COMPLETED，说明该批次已成功处理，删除本地备份文件
                Map<String, String> batchMeta = redisMetadataService.getBatchMetadata(fileId, sheetName, batchNum);
                if (batchMeta != null && "COMPLETED".equals(batchMeta.get("status"))) {
                    log.info("文件 {} Sheet {} Batch {}: Redis status 已经是 COMPLETED, 删除本地文件: {}",
                            fileId, sheetName, batchNum, fileName);
                    if (file.delete()) {
                        log.debug("删除本地备份文件: {}", fileName);
                    } else {
                        log.warn("删除本地备份文件失败: {}", fileName);
                    }
                    continue; // 跳过此文件
                }
                // 如果是 PENDING 或元数据不存在，继续恢复流程

                byte[] bytes = Files.readAllBytes(file.toPath());
                List<Student> students = objectMapper.readValue(bytes, new TypeReference<>() {});

                int batchSize = students.size();
                log.debug("加载本地备份文件 {}. FileId: {}, Sheet: {}, Batch: {}, Size: {}",
                        fileName, fileId, sheetName, batchNum, batchSize);

                // 保存数据回redis，更新元数据，发送trigger消息
                try {
                    // 1. 保存数据回redis
                    // 如果存 Redis 失败，会抛出异常，被外层 catch 捕获，保留本地备份
                    redisMetadataService.saveBatchData(fileId, sheetName, batchNum, students);
                    log.debug("文件 {} Sheet {} Batch {}: 保存数据回redis.", fileId, sheetName, batchNum);

                    // 2. 更新元数据状态PENDING
                    // 如果更新元数据失败，会抛出异常，被外层 catch 捕获，保留本地备份
                    redisMetadataService.updateBatchMetadata(fileId, sheetName, batchNum, batchSize, "PENDING");
                    log.debug("文件 {} Sheet {} Batch {}: 更新元数据状态PENDING", fileId, sheetName, batchNum);
                    // 恢复后也设置一下 metaKey 的过期时间，防止丢失
                    redisMetadataService.setMetaExpire(fileId, 24, TimeUnit.HOURS);

                    // 3. 发送trigger消息到MQ
                    // 这里的失败不会导致保留本地备份（如果前面都成功了），因为数据已经在 Redis 中了
                    boolean sentTrigger = producerService.sendAndConfirm(fileId, sheetName, batchNum, batchSize);

                    if (sentTrigger) {
                        log.info("文件 {} Sheet {} Batch {}: 本地备份恢复成功，数据已存入Redis，触发消息已发送至MQ，正在删除备份文件: {}",
                                fileId, sheetName, batchNum, fileName);
                        if (file.delete()) {
                            log.debug("删除备份文件: {}", fileName);
                        } else {
                            log.warn("删除备份文件失败: {}", fileName);
                        }
                    } else {
                        // 触发消息发送失败，数据已在 Redis 中，本地备份文件保留（为了 BackupRecoveryTask 的下次扫描）
                        log.warn("文件 {} Sheet {} Batch {}: 本地备份恢复过程中成功保存数据至Redis或发送触发消息: {}",
                                fileId, sheetName, batchNum, fileName);
                    }
                } catch (Exception e) {
                    log.error("文件 {} Sheet {} Batch {}: 本地备份恢复过程中未能成功保存数据至Redis或发送触发消息，暂保留备份文件: {}",
                            fileId, sheetName, batchNum, fileName, e);
                }
            } catch (IOException e) {
                log.error("读取或解析本地备份文件时出错{}.", fileName, e);
                // IO 异常，保留备份文件，可能需要人工检查
            } catch (Exception e) {
                log.error("处理本地备份文件时发生未知错误 {}.", fileName, e);
                // 其他异常，保留备份文件
            }
        }
    }

    // 2.处理 MQ 触发消息发送失败或消费者处理异常导致长时间 PENDING 的批次（可选）
    @Scheduled(fixedDelay = 5 * 60 * 1000) // 每5分钟扫描一次
    private void reTriggerStuckPendingBatches() {
        log.debug("开始扫描所有 PENDING 批次进行重触发.");
        // 调用 RedisMetadataService 查找所有 PENDING 批次
        List<Map<String, Object>> stuckBatches = redisMetadataService.findStuckPendingBatches();
        if (stuckBatches.isEmpty()) {
            log.debug("无 PENDING 批次需处理.");
            return;
        }

        log.info("发现 {} 个 PENDING 批次待重触发.", stuckBatches.size());
        for (Map<String, Object> batchInfo : stuckBatches) {
            String fileId = (String) batchInfo.get("fileId");
            String sheetName = (String) batchInfo.get("sheetName");
            int batchNum = (int) batchInfo.get("batchNum");
            int batchSize = (int) batchInfo.get("batchSize"); // 从元数据获取的批次大小

            log.info("重触发: File={}, Sheet={}, Batch={}", fileId, sheetName, batchNum);
            try {
                // 重发触发消息。 Consumer 会处理 Redis 数据是否存在的逻辑。
                boolean sentTrigger = producerService.sendAndConfirm(fileId, sheetName, batchNum, batchSize);
                if (!sentTrigger) {
                    // 如果重发触发消息失败 (未到达MQ交换机或未确认)，记录错误，该批次仍会保持 PENDING
                    log.error("重触发消息发送失败: File={}, Sheet={}, Batch={}", fileId, sheetName, batchNum);
                }
            } catch (Exception e) {
                // 如果发送过程中抛出异常，记录错误，该批次仍会保持 PENDING
                log.error("重触发异常: File={}, Sheet={}, Batch={}", fileId, sheetName, batchNum, e);
            }
        }
    }
}

